<?php
/** <p>The response to a request sent from HR_api.</p>
 * <p>The HR_apiResponse class deserialises the API response and prepares the data to be queried by classes that extend HR_api.</p>
 * @package com\hostingreborn\api
 *
 * @author      Jon Cram <jon@hostingreborn.com>
 * @copyright   https://api.hostingreborn.com/code/copyright/
 * @license     https://api.hostingreborn.com/code/license/
 * @version     1.0
 *
 * $Date$
 * $Revision$
 *
 *
 */
class HR_apiResponse {

    /***************************************************************************/
    /* Attributes                                                              */
    /***************************************************************************/


    /**
     *
     * @var string
     */
    private $responseXML = null;


    /**
     *
     * @var DOMDocument
     */
    private $responseDOM = null;


    /**
     *
     * @var DOMXPath
     */
    private $xPath = null;


    /***************************************************************************/
    /* Constructors                                                            */
    /***************************************************************************/

    public function __construct($responseXML = null, $url = null) {
        $this->responseXML = $responseXML;

        if (!$this->hasServerResponsed()) {
            throw new HR_apiResponseException(
            'no response from: \''.$url.'\'',
            100,
            '');
        }

        if ($this->isException()) {
            $this->handleExceptionResponse();
        }
    }

    /***************************************************************************/
    /* Public methods                                                          */
    /***************************************************************************/

    /**
     *
     * @param string $query XPath query
     * @return mixed
     */
    public function getValue($query = '', $type = null) {
        $rawValue = $this->responseXpath()->query($query.'/text()')->item(0)->wholeText;

        switch ($type) {
            case 'int':
                $value = (int)$rawValue;
                break;

            case 'float':
                $value = (float)$rawValue;
                break;

            case 'bool':
                $value = (bool)$rawValue;
                break;

            default:
                $value = $rawValue;
        }

        return $value;
    }


    /**
     *
     * @return boolean
     */
    public function wasSuccessful() {
        return $this->getValue('/success') == 'true';
    }


    /**
     *
     * @param array $queries
     * @return array
     */
    public function getCollection($queries = array()) {
        $collection = array();
        foreach ($queries as $query => $collectionKey) {
            $rawValue = $this->responseXpath()->query($query.'/text()')->item(0)->wholeText;

            if (is_string($collectionKey)) {
                $index = $collectionKey;
                $value = $rawValue;
            } else {
                $index = $collectionKey['index'];

                switch ($collectionKey['type']) {
                    case 'bool':
                        $value = (bool)$rawValue;
                        break;

                    case 'int':
                        $value = (int)$rawValue;
                        break;

                    case 'float':
                        $value = (float)$rawValue;
                        break;

                    default:
                        $value = $rawValue;
                        break;
                }
            }

            $collection[$index] = $value;
        }

        return $collection;
    }


    public function getSet($query = '') {
        $resultSet = $this->responseXpath()->query($query);
        
        $listLength = $resultSet->length;
        if ($listLength === 0) {
            return array();
        }

        $returnSet = array();
        for ($listIndex = 0; $listIndex < $listLength; $listIndex++) {
            $element = $resultSet->item($listIndex);
            $returnSet[] = new HR_apiResponse($element->ownerDocument->saveXML($element));
        }

        return $returnSet;
    }
    
    public function getSetValues($query) {
        $values = array();
        $set = $this->getSet($query);
        
        if (!count($set)) {
            return $values;
        }
        
        $queryParts = explode('/', $query);
        $valueIdentifier = '/'.$queryParts[count($queryParts) - 1];        
        
        foreach ($set as $currentItem) {
            $values[] = $currentItem->getValue($valueIdentifier);            
        }
        
        return $values;
    }


    /**
     *
     * @return string An XML representation of the server response
     */
    public function toXML() {
        return $this->responseDOM()->saveXML();
    }


    /**
     *
     * @return string returns toXML()
     */
    public function __toString() {
        return $this->toXML();
    }

    /***************************************************************************/
    /* Protected methods                                                       */
    /***************************************************************************/

    /***************************************************************************/
    /* Private methods                                                         */
    /***************************************************************************/

    /**
     * Did the server respond at all?
     *
     * @param string $serverResponse
     * @return boolean
     */
    private function hasServerResponsed() {
        return $this->responseXML !== false;
    }


    /**
     *
     * @throws HR_apiResponseException
     * @return DOMDocument
     */
    private function responseDOM() {
        if (!$this->responseDOM instanceof DOMDocument) {
            $this->responseDOM = new DOMDocument('1.0', 'utf-8');
            if (@$this->responseDOM->loadXML($this->responseXML) === false) {
                throw new HR_apiResponseException('API server returned malformed response', 101, $this->responseXML);
            }
        }

        return $this->responseDOM;
    }


    /**
     *
     * @return DOMXPath
     */
    private function responseXpath() {
        if (!$this->xPath instanceof DOMXPath) {
            $this->xPath = new DOMXPath($this->responseDOM());
        }

        return $this->xPath;
    }


    /**
     * Does this response represent an exception?
     *
     * @return boolean
     */
    private function isException() {
        return ($this->responseXpath()->evaluate('/exception')->length > 0);
    }


    /**
     * Process an XML-based exception response from the API
     * and throw a local PHP exception
     *
     * Common exceptions are handled and thrown here. All others
     * are thrown as generic unknown API exceptions to be handled
     * by whichever class extends the api class.
     *
     */
    private function handleExceptionResponse() {
        $type = $this->responseXpath()->query('/exception/type/text()')->item(0)->wholeText;
        $message = $this->responseXpath()->query('/exception/message/text()')->item(0)->wholeText;
        $code = $this->responseXpath()->query('/exception/code/text()')->item(0)->wholeText;

        switch ($type) {
            case 'database':
                $exceptionMessage = 'database connection failure';
                throw new HR_apiDatabaseException($message, $code);
                break;

            case 'system':
                $exceptionMessage = 'system failure';
                throw new HR_apiBackendSystemException($message, $code);
                break;

            case 'access':
                $exceptionMessage = 'access failure';
                throw new HR_apiAccessException($message, $code);

            default:
                $exception = new HR_apiResponseUnhandledException('Unhandled API exception');
                $exception->setApiExceptionDetails($type, $message, $code);

                throw $exception;
                break;
        }
    }

}

?>